package com.dylan.channel.v11;

import com.dylan.channel.v11.channel.AbstractNioChannel;

import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Logger;

/***
 * Description: Nio事件循环处理器
 * @Author: dylan
 * @Date: 2024/8/27 23:24
 */
public class NioEventLoop extends SingleThreadEventLoop {
    Logger logger = Logger.getLogger(NioEventLoop.class.getName());
    Selector selector;

    private Selector openSelector() {
        Selector wrapSelector = null;
        try {
            wrapSelector = SelectorProvider.provider().openSelector();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return wrapSelector;
    }

    public NioEventLoop() {
        selector = openSelector();
    }

    public void run() {
        for (; ; ) {
            try {

                select();

                processSelectKeys(selector.selectedKeys());

            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                runAllTask();
            }
        }
    }

    private void select() throws IOException {
        for (; ; ) {
            int select = selector.select(3000);
            if (select > 0 || hasTask()) {
                break;
            }
        }
    }

    private void processSelectKeys(Set<SelectionKey> keys) {
        Iterator<SelectionKey> iterator = keys.iterator();
        while (iterator.hasNext()) {
            SelectionKey key = iterator.next();
            iterator.remove();
            processSelectKey(key, (AbstractNioChannel) key.attachment());
        }
    }

    private void processSelectKey(SelectionKey key, AbstractNioChannel ch) {
        try {
            //得到key感兴趣的事件
            int ops = key.interestOps();
            //如果是连接事件
            if (ops == SelectionKey.OP_CONNECT) {
                //移除连接事件，否则会一直通知，这里实际上是做了个减法。位运算的门道，我们会放在之后和线程池的状态切换一起讲
                //这里先了解就行
                ops &= ~SelectionKey.OP_CONNECT;
                //重新把感兴趣的事件注册一下
                key.interestOps(ops);
                //然后再注册客户端channel感兴趣的读事件
                ch.doBeginRead();
            }
            //如果是读事件，不管是客户端还是服务端的，都可以直接调用read方法
            //这时候一定要记清楚，NioSocketChannel和NioServerSocketChannel并不会纠缠
            //用户创建的是哪个channel，这里抽象类调用就是它的方法
            //如果不明白，那么就找到AbstractNioChannel的方法看一看，想一想，虽然那里传入的参数是this，但传入的并不是抽象类本身，想想你创建的
            //是NioSocketChannel还是NioServerSocketChannel，是哪个，传入的就是哪个。只不过在这里被多态赋值给了抽象类
            //创建的是子类对象，但在父类中调用了this，得到的仍然是子类对象
            if (ops == SelectionKey.OP_READ) {
                ch.unsafe().read();
            }
            if (ops == SelectionKey.OP_ACCEPT) {
                ch.unsafe().read();
            }
        } catch (CancelledKeyException ignored) {
            throw new RuntimeException(ignored.getMessage());
        }
    }

    @Override
    public Selector getSelect() {
        return this.selector;
    }


}